package www.reagnetbox.com.activity;

import androidx.appcompat.app.AppCompatActivity;
import www.reagnetbox.com.R;
import www.reagnetbox.com.SerialPort;
import www.reagnetbox.com.adapter.RecentStockInListAdapter;
import www.reagnetbox.com.bean.ColRow;
import www.reagnetbox.com.bean.IdName;
import www.reagnetbox.com.bean.Instruction;
import www.reagnetbox.com.bean.RecentStockInListItem;
import www.reagnetbox.com.others.Const;
import www.reagnetbox.com.others.SerialPortConst;
import www.reagnetbox.com.scan.CmdUtils;
import www.reagnetbox.com.scan.CrvScannerDevice;
import www.reagnetbox.com.scan.IScannerListener;
import www.reagnetbox.com.scan.LogComm;
import www.reagnetbox.com.util.LitePalUtils;
import www.reagnetbox.com.util.RecyclerViewUtil;
import www.reagnetbox.com.util.SerialPortUtils;
import www.reagnetbox.com.util.ValidateUtil;
import www.reagnetbox.com.util.XPopupUtil;
import www.reagnetbox.com.view.AuthPopup;
import www.reagnetbox.com.view.EnterRowColPopup;
import www.reagnetbox.com.view.LoginPopup;
import www.reagnetbox.com.view.SelOutPortPopup;
import www.reagnetbox.com.view.SettingPopup;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.blankj.utilcode.util.SPUtils;
import com.gyf.barlibrary.ImmersionBar;
import com.kongqw.serialportlibrary.ConfigurationSdk;
import com.kongqw.serialportlibrary.Device;
import com.kongqw.serialportlibrary.SerialPortManager;
import com.kongqw.serialportlibrary.listener.OnOpenSerialPortListener;
import com.kongqw.serialportlibrary.listener.OnSerialPortDataListener;
import com.lxj.xpopup.core.CenterPopupView;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import static com.kongqw.serialportlibrary.listener.OnOpenSerialPortListener.Status.NO_READ_WRITE_PERMISSION;
import static com.kongqw.serialportlibrary.listener.OnOpenSerialPortListener.Status.OPEN_FAIL;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tvReagentTotalCount;
    private TextView tvReagentTypeCount;
    private TextView tvInventoryWarning;
    private TextView tvRemainPlace;
    private TextView tvTaskCount;
    private List<RecentStockInListItem> recentStockInList;
    private RecentStockInListAdapter recentStockInListAdapter;
    private Context context = this;

    private boolean useLocalFakeData = true;
    private SerialPort seriesPort;
    private SerialPortManager mSerialPortManager;
    private String TAG = "MainActivity";
    private TextView tvUserManage;
    private TextView tvCurUser;
    private SPUtils spInstance = SPUtils.getInstance();
    private String curUsername;
    private TextView tvLogout;
    private TextView tvReceivedData;
    private boolean openPortSuccess = false;
    private TextView tvSentData;
    private CrvScannerDevice scanner;
    int rcvLens = 0;
    byte[] mTtsCmd;
    private int sendTimes;
    private List<Instruction> datas = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImmersionBar.with(this).init();
        initView();
        initData();
//        initScan();
    }

    private void initView() {
        initTopViews();
        initCountStatViews();
        initRecentStockInListViews();
        initBottomButtonViews();
        initSerialPort("/dev/ttyS3");
    }

    private void initScan() {
        scanner = new CrvScannerDevice(this, new IScannerListener() {
            @Override
            public void onPermissonGranted() {
                openScanner();
            }

            @Override
            public void onRecv(byte[] data) {
                if (data != null) {
                    if (scanner.isHidPosMode() && data.length == 64) {
                        if (data[0] == 2) {
                            int len = data[1];
                            if (len > 0 && len <= 56) {
                                byte[] content = new byte[len];
                                System.arraycopy(data, 5, content, 0, len);
                                data = content;
//							LogComm.printf("hidpos dataLen:"+data.length);
                            }
                        }
                    }
                    rcvLens += data.length;
                    LogComm.printf("total lens:" + rcvLens + ",data lens:" + data.length);
//				LogComm.printf("data recv:" + CHexConver.byte2string(data));
                    if (data.length == 7 && (data[0] & 0xFF) == 0x7E && (data[1] & 0xFF) == 0x55
                            && ((data[4] & 0xFF) == 0x31)) {
                        runOnUiThread(() -> Toast.makeText(getApplicationContext(), "成功", Toast.LENGTH_SHORT).show());
                    } else {
                        try {
                            final String str = new String(data, "gbk");
                            runOnUiThread(() -> {
//                                    if (mTextRcv != null) {
//                                        mTextRcv.setText(mTextRcv.getText().toString() + str);
//                                        mScroll.fullScroll(ScrollView.FOCUS_DOWN);
//                                    }
                            });
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            @Override
            public void onDettach() {
                scanner.close();
            }

            @Override
            public void onAttach() {
                openScanner();
            }
        });
    }

    void openScanner() {
        if (scanner != null && !scanner.isOpened()) {
            try {
                scanner.open();
                if (scanner.isOpened()) {
                    mTtsCmd = CmdUtils.getTtsClearCmd();
                    if (scanner.isHidPosMode()) {
                        mTtsCmd = CmdUtils.toHidPos(mTtsCmd);
                    }
//                    mText.setText(CHexConver.byte2string(mTtsCmd));
//                    mBtn.setText("清空tts");

                    runOnUiThread(() -> {
                        if (scanner.isHidPosMode()) {
                            XPopupUtil.showKnowPopup(context, "hidpos模式");
                        } else if (scanner.isVcomMode()) {
                            XPopupUtil.showKnowPopup(context, "vcom模式");
                        }
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void initTopViews() {
        tvCurUser = findViewById(R.id.tv_cur_user);
        tvCurUser.setOnClickListener(this);
        tvUserManage = findViewById(R.id.tv_user_manage);
        tvUserManage.setOnClickListener(this);
        tvLogout = findViewById(R.id.tv_logout);
        tvLogout.setOnClickListener(this);
        if (Const.DEBUG) {
            findViewById(R.id.tv_test).setOnClickListener(this);

            View tvTestSerialPort = findViewById(R.id.tv_test_serial_port);
            tvTestSerialPort.setVisibility(View.VISIBLE);
            tvTestSerialPort.setOnClickListener(this);

            View tvTestSingleInstruction = findViewById(R.id.tv_test_single_instruction);
            tvTestSingleInstruction.setVisibility(View.VISIBLE);
            tvTestSingleInstruction.setOnClickListener(this);

            View tvTestMultiInstruction = findViewById(R.id.tv_test_multi_instruction);
            tvTestMultiInstruction.setVisibility(View.VISIBLE);
            tvTestMultiInstruction.setOnClickListener(this);
        }
        refreshUserViews();
    }

    private void refreshUserViews() {
        curUsername = spInstance.getString(Const.CUR_USERNAME);
        tvCurUser.setText(ValidateUtil.isStringValid(curUsername) ? curUsername : "登录");
        tvUserManage.setVisibility((ValidateUtil.isStringValid(curUsername) && curUsername.equals(Const.ADMIN_USERNAME)) ? View.VISIBLE : View.GONE);
        tvLogout.setVisibility(ValidateUtil.isStringValid(curUsername) ? View.VISIBLE : View.GONE);
    }

    private void initBottomButtonViews() {
        findViewById(R.id.iv_in).setOnClickListener(this);
        findViewById(R.id.iv_out).setOnClickListener(this);
        findViewById(R.id.iv_query).setOnClickListener(this);
        findViewById(R.id.iv_auth_login).setOnClickListener(this);
        findViewById(R.id.iv_setting).setOnClickListener(this);
    }

    private void initRecentStockInListViews() {
        recentStockInListAdapter = new RecentStockInListAdapter(this, R.layout.item_recent_stock_in_list, recentStockInList);
        recentStockInListAdapter.setOnItemClickListener((adapter, view, position) -> {
            RecentStockInListItem data = recentStockInList.get(position);
            XPopupUtil.showKnowPopup(context, data.getName());
        });
        RecyclerViewUtil.initRecyclerView(context, recentStockInListAdapter);
    }

    private void initCountStatViews() {
        tvTaskCount = findViewById(R.id.tv_task_count);
        tvReagentTotalCount = findViewById(R.id.tv_reagent_total_count);
        tvReagentTypeCount = findViewById(R.id.tv_reagent_type_count);
        tvInventoryWarning = findViewById(R.id.tv_inventory_warning);
        tvRemainPlace = findViewById(R.id.tv_remain_place);
    }

    private void initData() {
        if (spInstance.getBoolean(Const.FIRST_USE, true)) {
            LitePalUtils.saveUser("admin", "admin");
            spInstance.put(Const.FIRST_USE, false);
        }

        if (useLocalFakeData) {
            initFakeRecentStockInListData();
            initFakeNumData();
        } else {
            getNumData();
            getRecentStockInListData();
        }
    }

    private void getNumData() {
    }

    private void getRecentStockInListData() {

    }

    private void initFakeRecentStockInListData() {
        recentStockInList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            RecentStockInListItem item = new RecentStockInListItem();
            item.setName("测试试剂名称" + i);
            item.setNo(i);
            item.setCode("BMW002" + i);
            item.setStockInTime("2021-04-04 20:30:00");
            item.setNum(i * 3);
            item.setModel("100g/ml");
            recentStockInList.add(item);
        }
        recentStockInListAdapter.setNewData(recentStockInList);
    }

    private void initFakeNumData() {

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 必须调用该方法，防止内存泄漏
        ImmersionBar.with(this).destroy();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.tv_test:
                selTest();
                break;
            case R.id.tv_test_serial_port:
                testSerialPort();
                break;
            case R.id.tv_test_single_instruction:
                XPopupUtil.showCustomPopup(context, new TestSingleInstructionPopup(context));
                break;
            case R.id.tv_test_multi_instruction:
                testMultiInstructions();
                break;
            case R.id.tv_cur_user:
                if (!ValidateUtil.isStringValid(curUsername)) {
                    XPopupUtil.showCustomPopup(context, new LoginPopup(context, this::refreshUserViews));
                }
                break;
            case R.id.tv_user_manage:
                startActivity(new Intent(context, UserManageActivity.class));
                break;
            case R.id.tv_logout:
                XPopupUtil.showConfirmPopup(context, "提示", "确定退出登录?", () -> {
                    spInstance.put(Const.CUR_USERNAME, "");
                    refreshUserViews();
                });
                break;
            case R.id.iv_in:
                XPopupUtil.showCustomPopup(context, new AuthPopup(context, "入库鉴权", null, () -> {
//                    XPopupUtil.showCustomPopup(context, new StockInPopup(context, () -> {
//                        getRecentStockInListData();
//                        getNumData();
//                    }));
                }));
                break;
            case R.id.iv_out:
                XPopupUtil.showCustomPopup(context, new AuthPopup(context, "出库鉴权", null, () -> {

                }));
                break;
            case R.id.iv_query:
                startActivity(new Intent(context, QueryActivity.class));
                break;
            case R.id.iv_auth_login:
                break;
            case R.id.iv_setting:
                XPopupUtil.showCustomPopup(context, new SettingPopup(context));
                break;
        }
    }

    private void selTest() {
        List<String> ops = new ArrayList<String>() {{
            add("输入直径和位置,计算得出托盘的行号和列号");
            add("测试选择出料口弹窗");
            add("测试输入行列弹窗");
            add("获取结果码");
        }};
        XPopupUtil.singleSel(context, 0.6f, "请选择测试项", ops, null, (testPosition, text) -> {
            switch (testPosition) {
                case 0:
                    XPopupUtil.input(context,
                            0.5f,
                            "请输入直径(12,19,45)",
                            12 + "",
                            d -> XPopupUtil.input(context,
                                    0.2f,
                                    "请输入位置",
                                    0 + "",
                                    p -> {
                                        int diameter = Integer.parseInt(d);
                                        int position = Integer.parseInt(p);
                                        ColRow colRow = calcColRow(diameter, position);
                                        XPopupUtil.showKnowPopup(context, "列号=" + colRow.getCol() + "\n\n行号=" + colRow.getRow());
                                    }));
                    break;
                case 1:
                    XPopupUtil.showCustomPopup(context, new SelOutPortPopup(context, integer -> XPopupUtil.showKnowPopup(context, "value=" + integer)));
                    break;
                case 2:
                    XPopupUtil.showCustomPopup(context, new EnterRowColPopup(context, (col, row) -> XPopupUtil.showKnowPopup(context, "col=" + col + "\nrow=" + row)));
                    break;
                case 3:
                    String str = "7e3034200000200000203031203031200000200000200000200032";
                    XPopupUtil.showKnowPopup(context, "指令字符串=" + str + "\n\n结果码=" + SerialPortUtils.getResultCode(str));
                    break;
                case 4:
                    break;
                case 5:
                    break;
            }
        });
    }

    /*
    1.最小规格料盒(9+8+9)3排共计26支12mm,1.2ml安瓿瓶,整盘入料测试
    A)界面输入入料料架料盘,先调料盘到暂存区(0x30,0x34把库区的M行N列的料取出)PS:协议修改,先列再行
    B)从0-25,依次弹出入料规格瓶身直径规格xxmm,发送入料指令(0x30,0x31打开进料口,并准备托盘)收到应答,提示放入料.
      用户确认后把料放到暂存区料盘(0x30,0x32把料放入托盘的X行Y列位置),结束启动下一支料的入库操作.直至整盘入料结束.
      注意同一个料盘会有不同规格的料.
    C)把料盘入原料架(0x30,0x33把暂存区料盘放M行N列的库位)
    PS:所有的动作都要等到PLC反馈动作完成后,进行下一步

    2.中中等规格料盒(6+6)2排共计12支19mm,20ml安瓿瓶,整盘入料测试
    同1

    3.最大规格料盒(3+3)2排共计6支45mm,50ml西林瓶,整盘入料测试
    同1

    4.常温3*15个料架中所有的料盘移动到暂存区,再从暂存区移动回料架
    A)把常温区指定行列的料盘移动到暂存区(0x30,0x37把MN库列的料盘移动到暂存区)
    B)把暂存区的料盘移动到常温区指定MN行列(0x30,0x33把暂存区料盘放M行N列的库位)
    重复执行45次,确保每个料架上的料盘都操作过一遍

    5.每种规格物料各夹取一支料到A出料口以及B出料口
    A)把目标料盘移动到暂存区(0x30,0x37把料盘移动到暂存区)
    B)把指定试剂移动到出料口(0x30,0x35把暂存区的料放到指定出料口)(xy位置和出料口AB选择)
    c)打开出料口(0x30,0x36打开出料口)
    PS:3种料盘各操作一次

    6.移库操作,把2个同规格的料盒,反复进行互相移料操作.3种规格都需要验证
    A) 先将指定料盘移动到暂存区(0x30,0x37 把库区的M行N列的料取出)
    B) 料盒指定位置的料移动到入料料槽(30 41)
    C) 原料盒回归位置,把暂存区的料盘移动到常温区指定MN行列(0x30,0x33 把暂存区料盘放M行N列的库位)
    D) 目的料盒移到暂存区,先将指定料盘移动到暂存区(0x30,0x37 把库区的M行N列的料取出)
    E) 把入料料槽的料夹到目的料盒指定位置(30 32)
    F) 目的料盒回归位置,把暂存区的料盘移动到常温区指定MN行列(0x30,0x33 把暂存区料盘放M行N列的库位)*/
    private void testMultiInstructions() {
        List<String> ops = new ArrayList<String>() {{
            add("1.最小规格料盒(9+8+9)3排共计26支12mm,1.2ml安瓿瓶,整盘入料测试");
            add("2.中等规格料盒(6+6)2排共计12支19mm,20ml安瓿瓶,整盘入料测试");
            add("3.最大规格料盒(3+3)2排共计6支45mm,50ml西林瓶,整盘入料测试");
            add("4.常温3*15个料架中所有的料盘移动到暂存区,再从暂存区移动回料架");
            add("5.每种规格物料各夹取一支料到A出料口以及B出料口");
            add("6.移库操作,把2个同规格的料盒,反复进行互相移料操作(3种规格都需验证)");
        }};
        XPopupUtil.singleSel(context, 1.0f, "请选择测试项", ops, null, (position, text) -> {
            switch (position) {
                case 0:
                    testImport(SerialPortConst.SIZE_SMALL);
                    break;
                case 1:
                    testImport(SerialPortConst.SIZE_MEDIUM);
                    break;
                case 2:
                    testImport(SerialPortConst.SIZE_LARGE);
                    break;
                case 3:
                    testMovingPalletBetweenRackAndTempStore();
                    break;
                case 4:
                    testMovingItemToOutPort();
                    break;
                case 5:
                    break;
            }
        });
    }

    // 4.常温3*15个料架中所有的料盘移动到暂存区,再从暂存区移动回料架
    // aka：控制托盘在【料架】和【暂存区】来回移动
    private void testMovingPalletBetweenRackAndTempStore() {
        XPopupUtil.showCustomPopup(context, new EnterRowColPopup(context, (col, row) -> {
            // (A)把常温区指定行列的料盘移动到暂存区(0x30,0x37把MN库列的料盘移动到暂存区)
            Instruction a = new Instruction();
            a.setCommand(SerialPortConst.COMMAND_MOVE_PELLET_TO_LIB);
            a.setLibType(SerialPortConst.LIB_TYPE_NORMAL_TEMP);
            a.setLibCol(col);
            a.setLibRow(row);
            a.setEnterPalletRowCol(true);
            datas.add(a);

            // (B)把暂存区的料盘移动到常温区指定MN行列(0x30,0x33把暂存区料盘放M行N列的库位)
            Instruction b = new Instruction();
            b.setCommand(SerialPortConst.COMMAND_AXIS_BACK_TO_ORIGIN);
            b.setLibType(SerialPortConst.LIB_TYPE_NORMAL_TEMP);
            datas.add(b);

            sendData(datas.get(0), true);
        }));
    }

    // 5.每种规格物料各夹取一支料到A出料口以及B出料口
    // aka：把料移动到出料口
    private void testMovingItemToOutPort() {
        // A)把目标料盘移动到暂存区(0x30,0x37把料盘移动到暂存区)
        Instruction a = new Instruction();
        a.setCommand(SerialPortConst.COMMAND_MOVE_PELLET_TO_LIB);
        a.setSelOutPort(true);
        a.setEnterPalletRowCol(true);
        datas.add(a);

        // B)把指定试剂移动到出料口(0x30,0x35把暂存区的料放到指定出料口)(xy位置和出料口AB选择)
        Instruction b = new Instruction();
        b.setCommand(SerialPortConst.COMMAND_PUT_TEMP_PALLET_TO_OUT_PORT);
        datas.add(b);

        // C)打开出料口(0x30,0x36打开出料口)
        Instruction c = new Instruction();
        c.setCommand(SerialPortConst.COMMAND_OPEN_AB_PORT);
        datas.add(c);

        sendData(datas.get(0), true);
    }

    private void testImport(int diameter) {
        XPopupUtil.showCustomPopup(context, new EnterRowColPopup(context, (col, row) -> {
            // (A)界面输入入料料架料盘,先调料盘到暂存区(0x30,0x34把库区的M行N列的料取出)
            Instruction a = new Instruction();
            a.setCommand(SerialPortConst.COMMAND_MOVE_PELLET_TO_LIB);
            a.setLibCol(col);
            a.setLibRow(row);
            datas.add(a);

            int totalNumber = 0;
            switch (diameter) {
                case SerialPortConst.SIZE_SMALL:
                    totalNumber = 26;
                    break;
                case SerialPortConst.SIZE_MEDIUM:
                    totalNumber = 12;
                    break;
                case SerialPortConst.SIZE_LARGE:
                    totalNumber = 6;
                    break;
            }
            // (B)从0-25,依次弹出入料规格瓶身直径规格xxmm,
            // 发送入料指令(0x30,0x31打开进料口,并准备托盘)收到应答,提示放入料.
            // 用户确认后把料放到暂存区料盘(0x30,0x32把料放入托盘的X行Y列位置),
            // 结束启动下一支料的入库操作.直至整盘入料结束.
            // 注意同一个料盘会有不同规格的料.

            for (int i = 0; i < totalNumber; i++) {
                Instruction b1 = new Instruction();
                b1.setCommand(SerialPortConst.COMMAND_OPEN_PORT_AND_PREPARE_PALLET);
                b1.setSpecification(diameter);
                b1.setConfirmMsg("托盘准备完毕,请将试剂放置托盘上.\n放置结束后请按'确定'");
                datas.add(b1);

                Instruction b2 = new Instruction();
                b2.setCommand(SerialPortConst.COMMAND_PUT_ITEM_TO_PALLET);
                ColRow colRow = calcColRow(diameter, i);
                b2.setPalletRow(colRow.getRow());
                b2.setPalletCol(colRow.getCol());
                datas.add(b2);
            }

            // (C)把料盘入原料架(0x30,0x33把暂存区料盘放M行N列的库位)
            Instruction c = new Instruction();
            c.setCommand(SerialPortConst.COMMAND_AXIS_BACK_TO_ORIGIN);
            c.setLibCol(col);
            c.setLibRow(row);
            datas.add(c);

            XPopupUtil.showKnowPopup(context, 0.8f,"指令集(不含结果码)",getDatasContent());
//            sendData(datas.get(0), true);
        }));
    }

    private String getDatasContent() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < datas.size(); i++) {
            Instruction instruction = datas.get(i);
            sb.append(
                    "【" + i + "】"
                            + SerialPortUtils.instructionBeanToStringWithDivider(instruction, false, " - ")
                            + (i == datas.size() - 1 ? "" : "\n\n"));
        }
        return sb.toString();
    }

    ColRow calcColRow(int diameter, int position) {
        ColRow ret = new ColRow();
        switch (diameter) {
            case SerialPortConst.SIZE_SMALL:
                if (position <= 8) {
                    ret.setRow(0);
                    ret.setCol(position % 9);
                } else if (position <= 16) {
                    ret.setRow(1);
                    ret.setCol(position % 9);
                } else {
                    ret.setRow(2);
                    ret.setCol((position + 1) % 9);
                }
                break;
            case SerialPortConst.SIZE_MEDIUM:
                if (position <= 5) {
                    ret.setRow(0);
                } else {
                    ret.setRow(2);
                }
                ret.setCol(position % 6);
                break;
            case SerialPortConst.SIZE_LARGE:
                if (position <= 2) {
                    ret.setRow(0);
                } else {
                    ret.setRow(1);
                }
                ret.setCol(position % 3);
                break;
        }
        return ret;
    }

    private void testSerialPort() {
        List<String> ops = new ArrayList<String>() {{
            add("查看所有串口");
            add("打开串口(手动输入地址)");
            add("打开串口(从列表里选择)");
            add("关闭串口");
        }};
        XPopupUtil.singleSel(context, 0.6f, "请选择操作项", ops, null, (position, text) -> {
            switch (position) {
                case 0:
                    SerialPortUtils.showAllPorts(context);
                    break;
                case 1:
                    inputSerialPortPath();
                    break;
                case 2:
                    selPortToOpen();
                    break;
                case 3:
                    closePort();
                    break;
//                case 4:
//                    sendBytes();
//                    break;
//                case 5:
//                    sendInstructionBytes();
//                    break;
            }
        });
    }

    private void sendInstructionBytes() {
        Instruction instruction = new Instruction();
        // 命令
        instruction.setCommand(SerialPortConst.COMMAND_OPEN_PORT_AND_PREPARE_PALLET);
        // 库相关
        instruction.setLibType(SerialPortConst.LIB_TYPE_FREEZE_TEMP);
        instruction.setLibRow(12);
        instruction.setLibCol(12);
        // 托盘相关
        instruction.setPalletRow(12);
        instruction.setPalletCol(12);
        // 料口
        instruction.setPort(SerialPortConst.PORT_IN);

        byte[] bytes = SerialPortUtils.instructionBeanToBytes(instruction);
        boolean sendBytes = mSerialPortManager.sendBytes(bytes);
        Log.d(TAG, "【发送指令-sendBytes】" + "指令发送" + (sendBytes ? "成功" : "失败"));
        if (Const.DEBUG) {
            XPopupUtil.showKnowPopup(context, "指令发送" + (sendBytes ? "成功" : "失败"));
        }
    }

    private void sendBytes() {
        byte[] bytes = new byte[]{0x7e, 0x30, 0x32, 0x20, 0x30, 0x39, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x30, 0x30, 0x20,
                0x30, 0x38, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00};
        boolean sendBytes = mSerialPortManager.sendBytes(bytes);
        XPopupUtil.showKnowPopup(context, "发送" + (sendBytes ? "成功" : "失败"));
    }

    private void selPortToOpen() {
        List<Device> allPorts = SerialPortUtils.getAllPorts();
        if (ValidateUtil.isListValid(allPorts)) {
            List<String> ports = new ArrayList<>();
            for (Device device : allPorts) {
                ports.add(device.getName());
            }
            XPopupUtil.singleSel(context, 0.5f, "请选择要开启的串口", ports, null,
                    (position, text) -> {
                        String path = allPorts.get(position).getFile().getAbsolutePath();
                        initSerialPort(path);
                    });
        } else {
            XPopupUtil.showKnowPopup(context, "查无可用串口");
        }
    }

    private void inputSerialPortPath() {
        XPopupUtil.input(context,
                0.4f,
                "请输入串口路径",
                "/dev/ttyUSB0",
                this::initSerialPort);
    }

    private void initSerialPort(String portPath) {
        ConfigurationSdk sdk = new ConfigurationSdk.ConfigurationBuilder(new File(portPath), 9600)
                .log(TAG, true, false)
                .build();
        mSerialPortManager = SerialPortManager.getInstance();
        // 打开串口的监听
        mSerialPortManager.setOnOpenSerialPortListener(new OnOpenSerialPortListener() {
            @Override
            public void onSuccess(File device) {
                openPortSuccess = true;
                Log.d(TAG, "【串口开启结果监听-onSuccess】" + "串口(" + portPath + ")开启成功");
//                if (Const.DEBUG) {
//                    XPopupUtil.showKnowPopup(context, "串口(" + portPath + ")开启成功");
//                }
            }

            @Override
            public void onFail(File device, Status status) {
                openPortSuccess = false;
                String failReason = status == NO_READ_WRITE_PERMISSION ? "无读写权限" : (status == OPEN_FAIL ? "开启失败" : "");
                String info = "串口(" + portPath + ")开启失败:" + failReason;
                Log.d(TAG, "【串口开启结果监听-onFail】" + info);
                XPopupUtil.showKnowPopup(context, info);
            }
        });
        // 数据通信监听
        mSerialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() {
            @Override
            public void onDataReceived(byte[] bytes) {
                if (bytes == null) {
                    return;
                }
                Log.d(TAG, "【串口数据通信监听-onDataReceived】=" + SerialPortUtils.bytesToHex(bytes));
                if (tvReceivedData != null) {
                    tvReceivedData.setText(SerialPortUtils.bytesToHex(bytes));
                }
                SerialPortUtils.handleReceivedData(context, bytes, () -> {
                    if (ValidateUtil.isListValid(datas)) {
                        // 发送指令集里的某一条后，设备执行成功
                        String receivedStr = SerialPortUtils.removeResultCode(bytes);
                        if (receivedStr != null) {
                            for (int i = 0; i < datas.size(); i++) {
                                Instruction instruction = datas.get(i);
                                String str = SerialPortUtils.instructionBeanToString(instruction, false);
                                if (i == datas.size() - 1) {
                                    runOnUiThread(() -> Toast.makeText(context, "操作执行成功", Toast.LENGTH_SHORT).show());
                                } else {
                                    if (str.equals(receivedStr)) {
                                        instruction.setHasRespond(true);
                                        String confirmMsg = instruction.getConfirmMsg();
                                        boolean selOutPort = instruction.isSelOutPort();
                                        boolean enterPalletRowCol = instruction.isEnterPalletRowCol();
                                        Instruction nextInstruction = datas.get(i + 1);
                                        if (ValidateUtil.isStringValid(confirmMsg)) {
                                            // 需点击确认才能执行下一条指令
                                            runOnUiThread(() ->
                                                    XPopupUtil.showConfirmPopup(context, "提示", confirmMsg, () ->
                                                            sendData(nextInstruction, true)));
                                        } else if (selOutPort && enterPalletRowCol) {
                                            // 需【选择出料口】且【输入行列】能执行下一条指令
                                            // TODO: 2022/2/10
                                        } else if (selOutPort) {
                                            // 需【选择出料口】才能执行下一条指令
                                            runOnUiThread(() ->
                                                    XPopupUtil.showCustomPopup(context, new SelOutPortPopup(context, port -> {
                                                        nextInstruction.setPort(port);
                                                        sendData(nextInstruction, true);
                                                    })));
                                        } else if (enterPalletRowCol) {
                                            // 需【输入行列】才能执行下一条指令
                                            runOnUiThread(() ->
                                                    XPopupUtil.showCustomPopup(context, new EnterRowColPopup(context, (col, row) -> {
                                                        nextInstruction.setPalletCol(col);
                                                        nextInstruction.setPalletRow(row);
                                                        sendData(nextInstruction, true);
                                                    })));

                                        } else {
                                            sendData(nextInstruction, true);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    } else {
                        // 发送单条指令，设备执行成功
                        runOnUiThread(() -> Toast.makeText(context, "操作执行成功", Toast.LENGTH_SHORT).show());
                    }
                });
            }

            @Override
            public void onDataSent(byte[] bytes) {
                Log.d(TAG, "【串口数据通信监听-onDataSent】=" + (bytes != null ? SerialPortUtils.bytesToHex(bytes) : ""));
                if (tvSentData != null) {
                    tvSentData.setText(SerialPortUtils.bytesToHex(bytes));
                }
            }
        });
        mSerialPortManager.init(sdk, this);
    }

    void sendData(Instruction instruction, boolean clearTimes) {
        if (clearTimes) {
            sendTimes = 1;
        }
        byte[] bytes = SerialPortUtils.instructionBeanToBytes(instruction);
        boolean success = mSerialPortManager.sendBytes(bytes);
        if (success) {
            tvCurUser.postDelayed(() -> {
                if (!instruction.isHasRespond()) {
                    if (sendTimes == 3) {
                        XPopupUtil.showKnowPopup(context, "指令发送后机器无响应(已重发3次),请联系相关技术人员排查问题");
                    } else {
                        sendData(instruction, false);
                        sendTimes++;
                    }
                }
            }, 3 * 60 * 1000);
        } else {
            XPopupUtil.showKnowPopup(context, "指令发送失败");
        }
    }

    public class TestSingleInstructionPopup extends CenterPopupView {
        Context context;
        private EditText etSpecification;
        private EditText etLibRow;
        private EditText etLibCol;
        private EditText etPalletRow;
        private EditText etPalletCol;
        private RadioGroup rgLibType;
        private RadioGroup rgPort;
        private TextView tvCommand;

        public TestSingleInstructionPopup(Context context) {
            super(context);
            this.context = context;
        }

        @Override
        protected int getImplLayoutId() {
            return R.layout.popup_test_instruction;
        }

        @Override
        protected void onCreate() {
            super.onCreate();
            findViewById(R.id.tv_back).setOnClickListener(v -> dismiss());
            findViewById(R.id.tv_open_port).setOnClickListener(view -> selPortToOpen());
            findViewById(R.id.tv_close_port).setOnClickListener(view -> closePort());
            findViewById(R.id.tv_send).setOnClickListener(view -> {
                if (!openPortSuccess) {
                    XPopupUtil.showKnowPopup(context, "请先开启串口");
                    return;
                }
                Instruction instruction = getInstructionBean();
                byte[] bytes = SerialPortUtils.instructionBeanToBytes(instruction);
                boolean sendBytes = mSerialPortManager.sendBytes(bytes);
                Log.d(TAG, "【发送指令-sendBytes】" + "指令发送" + (sendBytes ? "成功" : "失败"));
                XPopupUtil.showKnowPopup(context, "指令发送" + (sendBytes ? "成功" : "失败"));
            });
            findViewById(R.id.tv_preview).setOnClickListener(view -> {
                Instruction instruction = getInstructionBean();
                byte[] bytes = SerialPortUtils.instructionBeanToBytes(instruction);
//                String detail = getInstructionPreview();
                XPopupUtil.showKnowPopup(context, "指令预览", 0.7f,
                        SerialPortUtils.bytesToHex(bytes));
            });

            tvCommand = findViewById(R.id.tv_command);
            List<IdName> commands = new ArrayList<>();
            commands.add(new IdName("3030", "运动件机械轴返回原点"));
            commands.add(new IdName("3031", "打开进料口,并准备托盘"));
            commands.add(new IdName("3032", "把料放入托盘的X行Y列位置"));
            commands.add(new IdName("3033", "把暂存区料盘放M行N列的库位"));

            commands.add(new IdName("3034", "把托盘暂存区的X行Y列的料放到指定出料口"));
            commands.add(new IdName("3035", "把暂存区的料盘X行Y列的料放到指定出料口"));
            commands.add(new IdName("3036", "打开A/B出料口"));
            commands.add(new IdName("3037", "把M行N列库区的料盘放到暂存区"));
            commands.add(new IdName("3031", "把库区的M行N列，托盘X行Y列的料移到进料暂存区"));

            commands.add(new IdName("3038", "打开冷藏柜门"));
            commands.add(new IdName("3039", "关闭冷藏柜门"));
            tvCommand.setOnClickListener(view -> XPopupUtil.singleSel(context, 0.8f, "请选择命令",
                    commands,
                    tvCommand.getText().toString(),
                    (position, text) -> {
                        tvCommand.setText(text);
                        tvCommand.setTag(commands.get(position).getStringId());
                    }));
            rgLibType = findViewById(R.id.rg_lib_type);
            findViewById(R.id.tv_clear_lib_type).setOnClickListener(view -> rgLibType.check(-1));
            rgPort = findViewById(R.id.rg_port);
            findViewById(R.id.tv_clear_port).setOnClickListener(view -> rgPort.check(-1));
            etSpecification = findViewById(R.id.et_specification);
            etLibRow = findViewById(R.id.et_lib_row);
            etLibCol = findViewById(R.id.et_lib_col);
            etPalletRow = findViewById(R.id.et_pallet_row);
            etPalletCol = findViewById(R.id.et_pallet_col);
            tvSentData = findViewById(R.id.tv_sent_data);
            tvReceivedData = findViewById(R.id.tv_received_data);
        }

        private Instruction getInstructionBean() {
            Instruction ret = new Instruction();
            // 命令
            if (tvCommand.getTag() != null) {
                ret.setCommand(tvCommand.getTag().toString());
            }
            // 规格
            if (!TextUtils.isEmpty(etSpecification.getText())) {
                ret.setSpecification(Integer.parseInt(etSpecification.getText().toString()));
            }
            // 库类
            int libTypeCheckedId = rgLibType.getCheckedRadioButtonId();
            if (libTypeCheckedId != -1) {
                ret.setLibType(libTypeCheckedId == R.id.rb_norma_temp ?
                        SerialPortConst.LIB_TYPE_NORMAL_TEMP :
                        SerialPortConst.LIB_TYPE_FREEZE_TEMP);
            }
            if (!TextUtils.isEmpty(etLibRow.getText())) {
                ret.setLibRow(Integer.parseInt(etLibRow.getText().toString()));
            }
            if (!TextUtils.isEmpty(etLibCol.getText())) {
                ret.setLibCol(Integer.parseInt(etLibCol.getText().toString()));
            }
            if (!TextUtils.isEmpty(etPalletRow.getText())) {
                ret.setPalletRow(Integer.parseInt(etPalletRow.getText().toString()));
            }
            if (!TextUtils.isEmpty(etPalletCol.getText())) {
                ret.setPalletCol(Integer.parseInt(etPalletCol.getText().toString()));
            }
            // 料口
            int portCheckedId = rgPort.getCheckedRadioButtonId();
            if (portCheckedId != -1) {
                switch (portCheckedId) {
                    case R.id.rb_port_in:
                        ret.setPort(SerialPortConst.PORT_IN);
                        break;
                    case R.id.rb_port_out_left:
                        ret.setPort(SerialPortConst.PORT_OUT_LEFT);
                        break;
                    case R.id.rb_port_out_right:
                        ret.setPort(SerialPortConst.PORT_OUT_RIGHT);
                        break;
                }
            }
            return ret;
        }

        private String getInstructionPreview() {
            StringBuilder ret = new StringBuilder();
            ret.append("固定开头：7e");
            // 命令
            ret.append("\n命令：");
            if (tvCommand.getTag() != null) {
                ret.append(tvCommand.getTag().toString());
            } else {
                ret.append("0000");
            }
            // 规格
            ret.append("\n规格：");
            if (!TextUtils.isEmpty(etSpecification.getText())) {
                int specification = Integer.parseInt(etSpecification.getText().toString());
                ret.append(SerialPortUtils.stringToAscii(specification + ""));
            } else {
                ret.append("0000");
            }
            // 库类
            ret.append("\n库类：");
            int libTypeCheckedId = rgLibType.getCheckedRadioButtonId();
            if (libTypeCheckedId != -1) {
                ret.append(libTypeCheckedId == R.id.rb_norma_temp ?
                        "3031" :
                        "3032");
            } else {
                ret.append("0000");
            }
            ret.append("\n库行：");
            if (!TextUtils.isEmpty(etLibRow.getText())) {
                int lRow = Integer.parseInt(etLibRow.getText().toString());
                ret.append(SerialPortUtils.stringToAscii(lRow + ""));
            } else {
                ret.append("0000");
            }
            ret.append("\n库列：");
            if (!TextUtils.isEmpty(etLibCol.getText())) {
                int lCol = Integer.parseInt(etLibCol.getText().toString());
                ret.append(SerialPortUtils.stringToAscii(lCol + ""));
            } else {
                ret.append("0000");
            }
            ret.append("\n托盘行：");
            if (!TextUtils.isEmpty(etPalletRow.getText())) {
                int pRow = Integer.parseInt(etPalletRow.getText().toString());
                ret.append(SerialPortUtils.stringToAscii(pRow + ""));
            } else {
                ret.append("0000");
            }
            ret.append("\n托盘列：");
            if (!TextUtils.isEmpty(etPalletCol.getText())) {
                int pCol = Integer.parseInt(etPalletCol.getText().toString());
                ret.append(SerialPortUtils.stringToAscii(pCol + ""));
            } else {
                ret.append("0000");
            }
            // 料口
            ret.append("\n料口：");
            int portCheckedId = rgPort.getCheckedRadioButtonId();
            if (portCheckedId != -1) {
                switch (portCheckedId) {
                    case R.id.rb_port_in:
                        ret.append("3031");
                        break;
                    case R.id.rb_port_out_left:
                        ret.append("3032");
                        break;
                    case R.id.rb_port_out_right:
                        ret.append("3033");
                        break;
                }
            } else {
                ret.append("0000");
            }
            return ret.toString();
        }
    }

    private void closePort() {
        if (mSerialPortManager != null) {
            XPopupUtil.confirm(context, "确定关闭串口?", () -> {
                mSerialPortManager.closeSerialPort();
                XPopupUtil.showKnowPopup(context, "串口关闭成功");
            });
        } else {
            XPopupUtil.showKnowPopup(context, "你还未开启串口(mSerialPortManager=null)");
        }
    }

    private void testWriteDataToSeriesPortByOrigin() {
        if (seriesPort == null) {
            XPopupUtil.showKnowPopup(context, "请先开启串口");
            return;
        }
        new Thread(() -> {
            String content = "Hello World";
            byte[] bytes = content.getBytes();
            OutputStream out = seriesPort.getOutputStream();
            try {
                out.write(bytes);
                out.flush();
                out.close();
            } catch (IOException e) {
                XPopupUtil.showKnowPopup(context, "数据写入失败", e.getMessage());
                e.printStackTrace();
            }
        }).start();
    }

    private void testReadDataFromSeriesPortByOrigin() {
        if (seriesPort == null) {
            XPopupUtil.showKnowPopup(context, "请先开启串口");
            return;
        }
        final InputStream inputStream = seriesPort.getInputStream();
        new Thread(() -> {
            try {
                byte[] buffer = new byte[1024];
                int size = inputStream.read(buffer);
                byte[] readBytes = new byte[size];
                System.arraycopy(buffer, 0, readBytes, 0, size);
                XPopupUtil.showKnowPopup(context, "读取到的来自串口的数据", new String(readBytes));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void testOpenSeriesPortByOrigin() {
        try {
            seriesPort = new SerialPort(new File("/dev/ttyS3"), 9600, 0);
        } catch (IOException e) {
            e.printStackTrace();
            XPopupUtil.showKnowPopup(context, "找不到该串口", "catch exception!\n" + e.getMessage());
        }
    }
}
